package com.xjd.edu.toolkit.contract;

import lombok.NoArgsConstructor;
import org.springframework.util.Assert;

import java.io.Serializable;

/**
 * 分页参数类，注：分页页码取值范围 [1, )，分页长度取值范围 [1, 1000]
 *
 * <pre>{@code
 * final Page page0 = Page.of(2, 30);
 * assertEquals(2, page0.getNumber());
 * assertEquals(30, page0.getSize());
 *
 * // 默认分页长度
 * final Page page2 = Page.of(3);
 * assertEquals(3, page2.getNumber());
 * assertEquals(Page.DEFAULT_PAGE_SIZE, page2.getSize());
 *
 * // 默认分页长度、默认分页页码
 * final Page page3 = Page.of();
 * assertEquals(Page.DEFAULT_PAGE_NUMBER, page3.getNumber());
 * assertEquals(Page.DEFAULT_PAGE_SIZE, page3.getSize());
 *
 * // 指定上轮查询最新ID（主键）、固定页码为1
 * final Page page4 = Page.ofWithLatestId(10086, 10);
 * assertEquals(Page.DEFAULT_PAGE_NUMBER, page4.getNumber());
 * assertEquals(10, page4.getSize());
 * assertEquals(10086, page4.getLatestId());
 *
 * // 指定上轮查询最新ID（主键）、默认分页长度、固定页码为1
 * final Page page5 = Page.ofWithLatestId(10086);
 * assertEquals(Page.DEFAULT_PAGE_NUMBER, page5.getNumber());
 * assertEquals(Page.DEFAULT_PAGE_SIZE, page5.getSize());
 * assertEquals(10086, page5.getLatestId());
 *
 * // 指定上轮查询最新ID（主键），可以为空，此时不影响页码取值
 * final Page page6 = Page.ofWithLatestId(null).setNumber(3);
 * assertEquals(3, page6.getNumber());
 * assertEquals(Page.DEFAULT_PAGE_SIZE, page6.getSize());
 * assertNull(page6.getLatestId());
 *
 * // 指定上轮查询最新ID（主键），取值非空，会影响页码取值（固定为1）
 * final Page page7 = Page.ofWithLatestId(10086).setNumber(3);
 * assertEquals(Page.DEFAULT_PAGE_NUMBER, page7.getNumber());
 * assertEquals(Page.DEFAULT_PAGE_SIZE, page7.getSize());
 * assertEquals(10086, page7.getLatestId());
 * }</pre>
 *
 * @author wangyu
 * @since 2022/2/28
 */
@NoArgsConstructor
public final class Page implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 最大分页长度，注：原定1000，但实际代码中有不少地方超限，为避免影响业务，暂定为10000，后续择机推进改进！
     */
    public static final int MAX_PAGE_SIZE = 10000;

    /**
     * 默认分页长度
     */
    public static final int DEFAULT_PAGE_SIZE = 20;

    /**
     * 默认分页页码
     */
    public static final int DEFAULT_PAGE_NUMBER = 1;

    /**
     * 分页页码 [1, )
     */
    private Integer number;
    /**
     * 分页长度 [1, 1000]
     */
    private Integer size;
    /**
     * 上次查询的最新ID，用于分页查询优化：当指定该参数时，页码将固定被重置为1
     */
    private Integer latestId;

    /**
     * 构造分页类
     *
     * @param number 分页页码，从1开始算，取值必须大于0
     * @param size   分页长度，取值必须大于0且小于等于最大分页长度（当前为：1000）
     * @return
     */
    public static Page of(int number, int size) {
        return new Page().setNumber(number).setSize(size);
    }

    /**
     * 构造分页类，分页长度取默认值：20
     *
     * @param number 分页页码，从1开始算，取值必须大于0
     * @return
     */
    public static Page of(int number) {
        return of(number, DEFAULT_PAGE_SIZE);
    }

    /**
     * 构造分页类，分页长度取默认值：20，分页页码取默认值：1
     *
     * @return
     */
    public static Page of() {
        return of(DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE);
    }

    /**
     * 构造分页类，分页页码取默认值：1，指定上轮查询最新ID和分页长度
     *
     * @param latestId
     * @param size
     * @return
     */
    public static Page ofWithLatestId(Integer latestId, int size) {
        return of(DEFAULT_PAGE_NUMBER, size).setLatestId(latestId);
    }

    /**
     * 构造分页类，分页长度取默认值：20，分页页码取默认值：1，指定上轮查询最新ID
     *
     * @param latestId
     * @return
     */
    public static Page ofWithLatestId(Integer latestId) {
        return ofWithLatestId(latestId, DEFAULT_PAGE_SIZE);
    }

    /**
     * 设置分页页码，从1开始算，取值必须大于0
     *
     * @param number
     * @return
     */
    public Page setNumber(int number) {
        Assert.isTrue(number > 0, "分页页码必须大于0");
        this.number = number;
        return this;
    }

    /**
     * 返回分页页码，当 latestId 值非空时，固定返回 1 （第1页）
     *
     * @return
     */
    public Integer getNumber() {
        if (latestId != null) {
            return DEFAULT_PAGE_NUMBER;
        }
        return number;
    }

    /**
     * 设置分页长度，取值必须大于0且小于等于最大分页长度（当前为：1000）
     *
     * @param size
     * @return
     */
    public Page setSize(int size) {
        Assert.isTrue(size >= 0 && size <= MAX_PAGE_SIZE, String.format("分页长度必须大于等于0且小于等于%d", MAX_PAGE_SIZE));
        this.size = size;
        return this;
    }

    /**
     * 保留一个不受限分页长度入口，以作兼容旧实现，注：极度不推荐使用！！！
     *
     * @param size
     * @return
     */
    @Deprecated
    public Page setSizeWithNoLimit(int size) {
        this.size = size;
        return this;
    }

    /**
     * 返回分页长度
     *
     * @return
     */
    public Integer getSize() {
        return size;
    }

    /**
     * 设置上次查询的最新ID，用于分页查询优化：当指定该参数时，页码将固定被重置为1
     *
     * @param latestId
     * @return
     */
    public Page setLatestId(Integer latestId) {
        this.latestId = latestId;
        return this;
    }

    /**
     * 返回上次查询的最新ID，用于分页查询优化
     *
     * @return
     */
    public Integer getLatestId() {
        return latestId;
    }

    /**
     * @return
     * @see #getSize()
     */
    public int size() {
        return this.getSize();
    }

    /**
     * 分页起始位置（从0开始计）
     *
     * @return
     */
    public int from() {
        return (this.getNumber() - 1) * this.getSize();
    }

}
